package security

import (
	"com.opennews.openplatform/web_api/shared"
	"github.com/dgrijalva/jwt-go"
)

// More details check on https://godoc.org/github.com/dgrijalva/jwt-go.

// It only holds user identification data which is embedded into others.
type UserIdentity struct {
	ID       string `json:"id"`
	Username string `json:"username"`
}

// Golang does not support the traditional OOP inheritance either.
// Even you can create a base structure and embed it into a child structure. You still can not pass child structure to a function which expects base structure.
// If you only need method you definitely can use interface. But how about common fields?
// interface does not support field in Golang.
// The solution I come up with is method for field. It likes get property method in Java.
// Now you can pass BaseClaims and MyClaims to function which accepts ClaimContainer interface.
type ClaimContainer interface {
	GetStandardClaims() *jwt.StandardClaims
	Valid() error
}

// Sets value only works on pointer type. We need to update on jwt.StandardClaims.
func (this *BaseClaims) GetStandardClaims() *jwt.StandardClaims {
	return &this.StandardClaims
}

func (this BaseClaims) Valid() error {
	return nil
}

// This is the basic token claims which is encoded into refresh token.
type BaseClaims struct {
	jwt.StandardClaims
	UserIdentity
}

// This contains more data likes Roles. It is encoded into access token.
type MyClaims struct {
	BaseClaims
	Roles []string `json:"roles"`
}

// This is anything client needs.
type MyUserDetails struct {
	UserIdentity
	FullName     string     `json:"fullName"`
	AvatarUrl    string     `json:"avatarUrl"`
	FullRoles    []FullRole `json:"fullRoles"`
	Bearer       string     `json:"bearer"`
	AccessToken  string     `json:"accessToken"`
	GeneratedAt  string     `json:"generatedAt"`
	ExpiredAt    string     `json:"expiredAt"`
	RefreshToken string     `json:"refreshToken"`
}

type FullRole struct {
	ID        string `json:"id"`
	Authority string `json:"authority"`
	Title     string `json:"title"`
}

// Generates token and returns token string and error.
// It accepts ClaimContainer interface. That means you can pass in either BaseClaims or MyClaims.
// By using interfaces is the way that how golang works.
func GenerateToken(claims ClaimContainer, signingKey string, expiresAt int64) (tokenString string, err error) {
	// Gets current date time for token issuedAt field.
	currentTime := shared.GetCurrentTime()

	// Sets when token issued.
	claims.GetStandardClaims().IssuedAt = currentTime.Unix()

	// Sets the expiresAt if needed.
	if expiresAt > 0 {
		claims.GetStandardClaims().ExpiresAt = currentTime.Unix() + expiresAt
	}

	// Creates token with provides claims.
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)

	// Gets token string with signing key.
	return token.SignedString([]byte(signingKey))
}

// Parses token string to Token structure.
func ParseToken(tokenString string, signingKey string) (claims *MyClaims, tokenParsed *jwt.Token, err error) {
	claims = &MyClaims{}

	// Parses input token string and updates claims.
	tokenParsed, err = jwt.ParseWithClaims(tokenString, claims, func(token *jwt.Token) (interface{}, error) {
		return []byte(signingKey), nil
	})

	if err != nil {
		return &MyClaims{}, nil, err
	}

	return claims, tokenParsed, nil
}
